package com.example.thread;
import java.util.LinkedList;
import java.util.NoSuchElementException;

// 本例其实就是演示的消费者、生产者的另一个版本。其他版本请参考CustomerAndProducerTest类。
// 18年某次面试某大厂，要求实时写一个小程序，要求如下：
// 1.两个线程，一个线程生产者，一个线程是消费者
// 2.生产者生产票，超过10张就休息，被消费了就继续生产。(也就就是库存的最大容量就是10嘛)
// 3.消费者消费票，票没了之后就休息，有票了接着消费。
// 题目看着很简单，但却包含了很多考点：消息队列、线程、线程通信、锁。
// 具体看看我写的源码，这是后期几经修改后的内容。
public class CustomerAndProducerTest02 {

    static String waitProp = new String(); // 这个后续做syncchorize里面的锁。
    static MyQueue<String> q = new MyQueue<>();

    // 具名内部类。相当于就是做一个仓库。
    /**
     * 自定义一个Queue，实现先进后出
     * 其实LinkedList内部对queue的各个方法实现更精妙，可以直接只用，这里主要为了抽出几个关键方法和属性来表达Queue的基础原理
     */
    static class MyQueue<T> {
        //使用LinkedList作为Queue存放消息
        private LinkedList<T> storage = new LinkedList<T>();

        public synchronized void push(T e) {//需要加上同步
            storage.addLast(e);
        }

        // 获取第一个元素。peek的本意是偷偷的看。
        public T peek() {
            T t = null;
            try {
                t = storage.getFirst();
            } catch (NoSuchElementException e) {}
            return t;
        }

        // 移除第一个元素
        public void pop() {
            storage.removeFirst();
        }

        // 返回布尔，判断仓库是否是空嘛
        public boolean empty() {
            return storage.isEmpty();
        }

        // 返回仓库目前库存量。
        public int size() {
            return storage.size();
        }

        @Override
        public String toString() {
            return storage.toString();
        }
    }

    //具名内部类。生产者对应的类。也是一个线程类的子类
    static class Provider extends Thread {

        @Override
        public void run() {
            while (true) { // 无限循环。
                q.push("piao");  // 如果按照这样生产票的话，那就是一次生产一张票。
                System.out.println("加票");
                //每次生产完了之后通知一下其他线程
                synchronized (waitProp) {
                    waitProp.notifyAll(); // 所有处于无限等待状态、定时等待状态的线程，至少会进入锁阻塞状态(如果调度的时候获取到锁，则进入可执行状态。)
                }
                // 生产超过仓库容量
                if (q.size() >= 10) {
                    System.out.println("加票满了");

                    synchronized (waitProp) {
                        try {
                            waitProp.wait(); // 让当前线程进入无限等待状态。进入等待之后，其实是不会走这句话后面的代码了。
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

                try {
                    Thread.sleep(300L); // 进入定时等待状态。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //具名内部类。消费者线程。线程类的一个子类。
    static class Comsumer extends Thread {

        @Override
        public synchronized void run() {
            while (true) {
                String a = q.peek(); // 获取仓库第一个元素。如果按照这样消费的话，其实就是一次消费一张票。
                System.out.println("取票");
                //每次消费完了之后通知一下等待中的生产者
                synchronized (waitProp) {
                    waitProp.notifyAll();
                }
                if (a == null) {
                    System.out.println("取票没了");

                    synchronized (waitProp) {
                        try {
                            waitProp.wait(); // 自己的消费线程进入等待。进入等待之后，后面的代码都不会走哦。
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                try {
                    Thread.sleep(500L); // 这个可能就是假装模拟消费一张票所需要的时间吧。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 在main方法里面测试
    public static void main(String[] args){
        Provider p = new Provider();
        Comsumer c = new Comsumer();
        // 调用生产者、消费者线程之后的start方法之后，会对应地调用线程的的run方法。
        p.start();
        c.start();
    }

}

// 本案例实际执行的结果如下，每次打印不一定完全一样哦：
// 加票
// 取票
// 取票没了
// 加票
// 加票
// 取票
// 加票
// 加票
// ... ...



